import * as THREE from "../../../modules/three/three.module.js";
import {BingMapsProvider, MapBoxProvider, MapView, UnitsUtils} from "../../../modules/geo-three/geo-three.module.js";
import {TilesRenderer} from "../../../modules/3d-tiles-renderer/src/index.js";
import {DRACOLoader} from "../../../modules/three/jsm/loaders/DRACOLoader.js";
import {GLTFLoader} from "../../../modules/three/jsm/loaders/GLTFLoader.js";
import {FlyOrbitControls} from "../../../modules/three/jsm/controls/FlyOrbitControls.js";
import {DRACO_DECODER_PATH, epsg_4544, KeyCode, MouseButton, r2d, WorldConfig} from "../lib/CommonConsts.js";
import MainStore from "../model/MainStore.js";
import Cartesian3 from "../lib/cesium/Core/Cartesian3.js";
import Cartographic from "../lib/cesium/Core/Cartographic.js";
import SelectWorkAreaPlugin from "../plugins/SelectWorkAreaPlugin.js";
import {createEyeLocatorMarker, getMouseRaycaster, getPositionMarker, getYAngleDeg} from "../lib/util/StaticMethods.js";
import AddSprayPathPointPlugin from "../plugins/AddSprayPathPointPlugin.js";
import {getLocalItem, setLocalItem} from "../lib/util/local-storage-tool.js";
import {SprayPathAnimation} from "../animate/SprayPathAnimation.js";
import SprayPathReader from "../lib/gadget/SprayPathReader.js";
import World3dEvent from "./event/World3dEvent.js";
import FlyPathIndicatorV2 from "../lib/gadget/FlyPathIndicatorV2.js";
import DroneFlyPath, {pathFromAI} from "../model/DroneFlyPath.js";
import MeasureUtil, {hideWorkAreaTip} from "./MeasureUtil.js";
import EditRecord from "./undo/EditRecord.js";
import ModifyVertexPlugin from "../plugins/ModifyVertexPlugin.js";
import SelectHoleAreaPlugin from "../plugins/SelectHoleAreaPlugin.js";
import SelectFixedEyePlugin from "../plugins/SelectFixedEyePlugin.js";
import BlockingMeshPlugin from "../plugins/BlockingMeshPlugin.js";
import EditOrientation from "./EditOrientation.js";
import EditWorkArea from "./EditWorkArea.js";
import EditHoleArea from "./EditHoleArea.js";
import ScaleControl, {eventScaleChanged} from "../lib/gadget/ScaleControl.js";
import TransformRotateControl, {eventDraggingChanged} from "../lib/gadget/TransformRotateControl.js";
import OutlineHelper from "../lib/shader/OutlineHelper.js";
import PathComputeUI from "../pathCompute/PathComputeUI.js";
import RoutePointPlugin from "../plugins/RoutePointPlugin.js";
import PlaneRefVO from "../model/PlaneRefVO.js";
import PlaneRefMesh from "../lib/gadget/PlaneRefMesh.js";
import IsoLinePlugin from "../plugins/IsoLinePlugin.js";
import SurfaceGridPlugin from "../plugins/SurfaceGridPlugin.js";
import RoutePointIndicator from "../lib/gadget/RoutePointIndicator.js";
import {
  DirectEnum,
  generateRectAreaSprayPath,
  generateSprayPath,
  SprayConfig
} from "../pathCompute/SprayPathCompute.js";
import SprayPathIndicator from "../lib/gadget/SprayPathIndicator.js";
import SurfacePartitionPlugin from "../plugins/SurfacePartitionPlugin.js";
import {removeArrayItem} from "../lib/util/ToolMethods.js";

// BoolPolygonsUtil.setPrecision(3)
// true 表示 每帧不自动重绘，需要手动调用render方法进行重绘，因为代码中可能存在不明原因的内存泄露；手动进行重绘，可以使内存问题延迟暴露
const renderManually = false;
let renderCount = 0
let renderInterval = -1
let posIndicator
// b3dms 3d tile 模型的离地高度
let offsetZ = -35;
const useMapbox = false;
const startPos = new THREE.Vector2()
const endPos = new THREE.Vector2()
let pointerEverMoved = false

const subViewRect0 = new THREE.Vector4(
  32 / devicePixelRatio,
  2 / 3,
  1 / 4,
  1 / 4,
);
const subViewRect = new THREE.Vector4();
const oldCamPos = new THREE.Vector3();
const oldTargetPos = new THREE.Vector3();
let dragStart = new THREE.Vector3();

const _quatZero = new THREE.Quaternion()
const negative_z = new THREE.Vector3(0, 0, -1);
const unit_x = new THREE.Vector3(1, 0, 0);
const offset = new THREE.Vector3();
let aAxis = new THREE.Vector3();
const tmp1 = new THREE.Vector3();
const tmp2 = new THREE.Vector3();
const tmp3 = new THREE.Vector3();
const dirForward = new THREE.Vector3();
const dirRight = new THREE.Vector3();
const tmpPoint = new THREE.Vector3();
let aAngle = 0;

const animFrame =  20 // 相机动画帧数
let quaterionStep
let quaterionChanged = true
const cameraMove = new THREE.Vector3()
const cameraNewPosition = new THREE.Vector3()
const cameraDistances = []
let cameraMoving = false
let curFrame = 0

function getSubViewRect(width, height) {
  subViewRect.x = subViewRect0.x;
  subViewRect.y = subViewRect0.y * height;
  subViewRect.z = subViewRect0.z * width;
  subViewRect.w = subViewRect0.w * height;
  return subViewRect;
}

const AttachEnum = {
  move: 'move',
  scale: 'scale',
}

const mouseRaycaster = new THREE.Raycaster()

export default class World3d {
  /**
   *
   * @type {THREE.Scene}
   */
  scene
  /**
   * 当前使用的相机
   * @type {THREE.PerspectiveCamera | THREE.OrthographicCamera}
   */
  camera
  /**
   * 透视相机
   * @type {THREE.PerspectiveCamera}
   */
  _perspCamera
  /**
   * 正交相机
   * @type {THREE.OrthographicCamera}
   */
  _orthoCamera
  _cameraThird;
  _usingCameraThird = false;
  _eyeLocator;
  _mousedownOnSub = false;
  /**
   * @type {THREE.WebGLRenderer}
   */
  renderer
  /**
   * @type {FlyOrbitControls}
   */
  cameraControls
  /**
   * b3dms 瓦片加载器
   * @type {TilesRenderer}
   */
  tiles
  /**
   *@type {THREE.Group}
   */
  offsetParent;
  /**
   *@type {THREE.Group}
   */
  earthGroup;
  // true 跳过渲染
  _skipRender = false
  _attachState = AttachEnum.move
  _quat = new THREE.Quaternion();

  /**
   * @type {TransformRotateControl}
   */
  transformRotateControl;
  scaleControl;
  _modelUrl = '';

  /**
   * @type {THREE.Vector3}
   */
  cameraOldPosition = new THREE.Vector3()
  /**
   * 从相机当前位置指向相机焦点
   * @type {THREE.Vector3}
   */
  cameraFacing = new THREE.Vector3()
  /**
   * 相机的目标朝向
   * @type {THREE.Vector3}
   */
  cameraFaceTo = new THREE.Vector3()
  mouseRaycaster = new THREE.Raycaster()
  editOrientation;
  editWorkArea;
  editHoleArea;
  measureUtil;
  editRecord = new EditRecord();
  _animations = []
  // 可被编辑对象的插件列表
  _plugins = []
  _modifyVertPlugin;
  _selectAreaPlugin;
  _selectHolePlugin;
  _selectFixedEyePlugin;
  _addBlockingPlugin;
  _addSprayPathPointPlugin
  _addRoutePointPlugin
  _addIsoLinePlugin
  _surfaceGridPlugin
  _surfacePartitionPlugin
  /**
   * 数据中心
   * @type {MainStore}
   */
  store
  /**
   * 二维的gis瓦片地图
   * @type {MapView}
   */
  mapView
  /**
   * 为选中的物体显示轮廓
   * @type {OutlineHelper}
   */
  outlineHelper;
  /**
   * 负责加载 AI规划出来的喷涂航线，并进行数据格式转换
   * @type {SprayPathReader}
   */
  pathReader;
  _showingWireframe = false;
  // true 表示需要锁定 tile网格瓦片的更新
  _lockTileUpdate = false;

  b3dmsClipPlanes = []

  // this.loadB3dmsModel 加载的b3dms瓦片模型时，获取的元数据。Python做航线规划时，需要 toEarchCoordMat矩阵 把零变换坐标 转换为地球直角坐标系坐标
  _meta = {
    // 地球直角坐标系的定义是：原点O与 地球质心 重合，Z轴指向地球北极，X轴指向地球赤道面与格林尼治 子午圈 的交点，Y轴在 赤道平面 里与XOZ构成 右手坐标系
    // 三维世界坐标 乘以此矩阵 得到地球直角坐标系(正Z轴指向地球北极，正X轴指向本初子午线，正Y轴指向东经90度) 坐标 (模型元数据)
    toEarchCoordMat: new THREE.Matrix4(),
    toEarchCoordMatInv: new THREE.Matrix4(),
    modelEarthMat: new THREE.Matrix4(),
    // 球面上的二维经纬度坐标 根据特定的gis投影坐标系 转换为和大疆智图一致的投影坐标，便于测量3d模型上，点与点之间的距离，区域面积，体积 (模型元数据)
    // epsgCoord 是由飞手 在航拍建模时，根据所在地区，选择合适的投影坐标系，一般根据地区的经纬度和大小等位置信息确定
    epsgCoord: epsg_4544,
  }

  // this.loadB3dmsModel() 执行成功后 this._invalidModelUrl 为 false
  _invalidModelUrl = true;
  // 暂存 websocket 子任务航点集数据的推送内容
  _subRouteMessages

  init(canvas = undefined) {
    this.scene = new THREE.Scene()
    this.axesHelper = new THREE.AxesHelper(2000)
    this.scene.add(this.axesHelper);
    this._dimension = {w: window.innerWidth, h: window.innerHeight, dom: canvas, sizeComputed: false}
    const aspect = window.innerWidth / window.innerHeight
    this._perspCamera = new THREE.PerspectiveCamera(80, aspect, 0.01, 1e12)
    this._perspCamera.up.set(0, 0, 1)
    const vs = 2 // vertical size
    const hs = vs * aspect // horizontal size
    this._orthoCamera = new THREE.OrthographicCamera(-hs, hs, vs, -vs, 0.01, 4000)
    this._orthoCamera.up.set(0, 0, 1)
    this._orthoCamera.position.set(0, 20, 0)
    this._eyeLocator = createEyeLocatorMarker()
    this.camera = this._perspCamera
    this.renderer = new THREE.WebGLRenderer({antialias: true, canvas: canvas})
    this.renderer.outputEncoding = THREE.sRGBEncoding
    this.renderer.localClippingEnabled = true
    this.renderer.setSize(window.innerWidth, window.innerHeight, false)
    this.renderer.setClearColor('#fff', 1)
    this.outlineHelper = new OutlineHelper(this.renderer, this.scene, this.camera);
    if (!canvas) {
      document.body.appendChild( this.renderer.domElement );
    }

    const cameraControls = new FlyOrbitControls(this.camera, this.renderer.domElement)
    cameraControls.screenSpacePanning = true; // 相对于屏幕平移， 编辑 立面 墙面时更方便
    cameraControls.minDistance = 0.3;
    cameraControls.maxDistance = 9000;
    this.cameraControls = cameraControls;
    this.resetCameraPosition();
    if (renderManually) {
      const renderFrame = () => {
        this.render();
      };
      this.cameraControls.addEventListener('change', renderFrame.bind(this));
    }
    this._createLights();
    this.transformRotateControl = new TransformRotateControl(this.camera);
    // this.transformRotateControl.setRotateZOnly();
    this.transformRotateControl.addEventListener(
      eventDraggingChanged.type,
      (event) => {
        cameraControls.enabled = !event.value;
        if (!event.rotated) {
          return
        }
        const obj = this.transformRotateControl.object
        if (obj instanceof PlaneRefMesh) {
          obj.updateVO()
        }
      },
    );
    this.scene.add(this.transformRotateControl);
    this.scaleControl = new ScaleControl(this.camera);
    this.scaleControl.addEventListener(eventScaleChanged.type, (event) => {
      cameraControls.enabled = !event.value;
    });
    this.scene.add(this.scaleControl);

    this.keydownHandler = this._keydownHandler.bind(this)
    this.onWindowResize = this._onWindowResize.bind(this)
    window.addEventListener('keydown', this.keydownHandler)
    window.addEventListener('resize', this.onWindowResize)

    this.store = new MainStore(this)
    this.editOrientation = new EditOrientation(this);
    this.editWorkArea = new EditWorkArea(this);
    this.editHoleArea = new EditHoleArea(this);
    this.measureUtil = new MeasureUtil(this);
    this._initPlugins();
    this.initMapView()
    // this.initDroneFlyPath(pathFromAI)
    // setTimeout(() => {
    //   this.store.updateFlyPathProgress(0);
    //   this.store.showDroneMesh(104.0387258, 30.7371236, 503, 30)
    // }, 3000)
    this.cameraControls.addEventListener('change', () => {
      this.editOrientation.updateEyeIndicatorsVisible();
    });

    if (renderManually) {
      renderCount = 0;
      const renderFrame = () => {
        this.render();
        renderCount++;
      };
      renderInterval = setInterval(() => {
        renderFrame();
        if (renderCount > 60) {
          clearInterval(renderInterval);
        }
      }, 50);
    }
    this._initDragDrop();
  }

  _initPlugins() {
    // 创建 插件
    this._modifyVertPlugin = new ModifyVertexPlugin(this);
    this._selectAreaPlugin = new SelectWorkAreaPlugin(this);
    this._selectHolePlugin = new SelectHoleAreaPlugin(this);
    this._selectFixedEyePlugin = new SelectFixedEyePlugin(this);
    this._addSprayPathPointPlugin = new AddSprayPathPointPlugin(this);
    this._addBlockingPlugin = new BlockingMeshPlugin(this);
    this._addRoutePointPlugin = new RoutePointPlugin(this);
    this._addIsoLinePlugin = new IsoLinePlugin(this);
    this._surfaceGridPlugin = new SurfaceGridPlugin(this);
    this._surfacePartitionPlugin = new SurfacePartitionPlugin(this);
    this._plugins.push(
      this._modifyVertPlugin,
      this._selectHolePlugin,
      this._selectAreaPlugin,
      this._selectFixedEyePlugin,
      this._addSprayPathPointPlugin,
      this._addBlockingPlugin,
      this._addRoutePointPlugin,
      this._addIsoLinePlugin,
      this._surfaceGridPlugin,
      this._surfacePartitionPlugin,
    );
  }

  resetCameraPosition() {
    this.camera.position.set(-185, 38, 300);
    this.cameraControls.target.set(0, 0, 0);
    this.cameraControls.update();
  }

  sendMsgByQtSocket(msg) {
    if (this.qtSocket) {
      this.qtSocket.sendMsgToStation(msg);
    }
  }

  // 快捷键支持
  _keydownHandler(event) {
    if (event.target !== document.body) {
      return
    }
    // const transformControl = this.transformControl
    switch (event.code) {
      case KeyCode.F:
        this.store.focusTarget();
        this._addIsoLinePlugin.setUsingIndicator();
        break;
      case KeyCode.L:
        if (this.store.pickingRoutePoint()) {
          this.store.pickRoute()
        } else {
          this.transformRotateControl.setMoveFromLocalSpace(
            !this.transformRotateControl.getMoveFromLocalSpace(),
          );
        }
        break;
      case KeyCode.H:
        this.measureUtil.toggleGrid(!this.measureUtil.showingGrid());
        break;
      case KeyCode.I:
        if (event.ctrlKey) {
          return;
        }
        this._showingWireframe = !this._showingWireframe;
        break;
      case KeyCode.M:
        if (this.store.pickingRoutePoint()) {
          this.store.movingRoutePoint()
        } else if (this._addIsoLinePlugin._selectedIndicator) {
          this.store.movingNormalPoint()
        } else {
          if (this._surfacePartitionPlugin.rotateTool.visible) {
            this._surfacePartitionPlugin.toggleMarking()
          } else {
            if (event.shiftKey) {
              this.measureUtil.clearMarks();
            } else {
              this.measuringPoint();
            }
          }
        }
        break;
      case KeyCode.N:
        this.measureUtil.toggleWorkAreaMarks();
        this.measureUtil.togglePlaneRefMarks();
        break;
      case KeyCode.O:
        this.measureUtil.toggleMeasuringPlaneRef();
        break;
      case KeyCode.P:
        this._cameraThird = this.camera.clone();
        this._usingCameraThird = !this._usingCameraThird;
        this.renderer.autoClear = !this._usingCameraThird;
        if (this._usingCameraThird) {
          if (this.camera === this._orthoCamera) {
            this._eyeLocator.scale.copy(this._eyeLocator.userData.scaleOrtho);
          } else {
            this._eyeLocator.scale.copy(this._eyeLocator.userData.scalePersp);
          }
        }
        break;
      case KeyCode.R:
        if (this.transformRotateControl.object) {
          this.scaleControl.attach(this.transformRotateControl.object);
          this.transformRotateControl.detach();
          this._attachState = AttachEnum.scale;
        } else if (this.scaleControl.object) {
          this.transformRotateControl.attach(this.scaleControl.object);
          this.scaleControl.detach();
          this._attachState = AttachEnum.move;
        }
        break;
      case KeyCode.T:
        this._surfacePartitionPlugin.toggleScaleControlVisible(!this._surfacePartitionPlugin.scaleTool.visible)
        if (this.store.stateAddingRoutePoint) {
          this._addRoutePointPlugin.fastLocatePoint()
        } else if (this.store.pickingSprayPathIndicator()) {
          const pathIndicator = this.store.pickingSprayPathIndicator()
          const routeVO = pathIndicator.switchToManualRoute()
          if (routeVO) {
            pathIndicator.removeFromParent()
            removeArrayItem(this.store.sprayPathIndicators, pathIndicator)
            const lastIndex = routeVO.pointsByJson.length - 1
            routeVO.pointsByJson.forEach((routePoint, index) => {
              const indicator = new RoutePointIndicator(routePoint, this.scene)
              routeVO.pointMeshes.push(indicator)
              this.store.routePoints.push(indicator)
              this.scene.add(indicator)
              if (index === 0) {
                indicator.markAsStart()
              } else if (index === lastIndex) {
                indicator.markAsEnd()
              }
            })
            routeVO.pointMeshes.forEach((pointMesh, index) => {
              if (index > 0) {
                const pre = routeVO.pointMeshes[index - 1].vo
                pointMesh.createLine(this.scene, pre)
              }
            })
            this.store.routeList.push(routeVO)
          }
        } else if (this.store.stateFree) {
          this._addRoutePointPlugin.setRoutePointHeading()
          this._addIsoLinePlugin.setNormalPointOrient()
          if (this.store.pickingPlaneRef()) {
            const obj = this.transformRotateControl.object
            if (obj && obj instanceof PlaneRefMesh) {
              this.transformRotateControl.detach()
              this.transformRotateControl.setTranslateVisible(true)
            } else {
              this.transformRotateControl.attach(this.store.pickingPlaneRef());
              this.transformRotateControl.setTranslateVisible(false)
            }
          }
        }
        break;
      case KeyCode.U:
        // this.editOrientation.shapeExtract.computeClosedAreaGeometry4Debug();
        this.toggleTileLock(true)
        break;
      case KeyCode.V:
        this.measureUtil.toggleHeightMeasuring(
          !this.measureUtil.measuringHeight(),
        );
        break;
      case KeyCode.BACK:
      case KeyCode.DEL:
        this.allPluginsDetach();
        this.editWorkArea.removeAreaMesh();
        this.editHoleArea.removeHoleMesh();
        this.editOrientation.removeEyeIndicator();
        this.editOrientation.removeSampleCube();
        this.store.removeSprayPath();
        this.store.removeCurrentHoverPoint();
        this.store.removeBlocking();
        this.store.removePlaneRef();
        this.store.removeSurfaceMesh();
        this.store.removePickingRoute();
        this.store.removeRoutePoint();
        this._addIsoLinePlugin.removeNormalPoint();
        this._addIsoLinePlugin.removeIsoLine();
        this._surfaceGridPlugin.removeTriangleMesh()
        break;
      case KeyCode.Equal:
      case KeyCode.Minus:
      case KeyCode.Digit0:
        this._addIsoLinePlugin.changeIndicatorBottom(event.code);
        break;
      case KeyCode.Z: {
        const opRecord = this._addSprayPathPointPlugin.opRecord;
        if (event.ctrlKey) {
          if (event.shiftKey) {
            this.editRecord.redoAction();
            if (opRecord) {
              opRecord.redoAction();
            }
          } else {
            this.editRecord.undoAction();
            if (opRecord) {
              opRecord.undoAction();
            }
            // this.editWorkArea.endAddOrEditWorkArea()
          }
        } else {
          this.startSetTarget();
        }
        break;
      }
      default:
        break;
    }
    if (renderManually) {
      this.render();
    }
  }

  _onWindowResize() {
    this.sendMsgByQtSocket('-------3d编辑 检测到视口改变');
    this._dimension.sizeComputed = false;
    this._updateForDimensionChange();
  }

  attachObject(obj3d) {
    if (this._attachState === AttachEnum.move) {
      this.transformRotateControl.attach(obj3d);
      this.scaleControl.detach();
    } else {
      this.scaleControl.attach(obj3d);
      this.transformRotateControl.detach();
    }
  }
  detachObject() {
    const tControl = this.transformRotateControl;
    const sControl = this.scaleControl;
    if (tControl.object && !tControl.highlighting) {
      tControl.detach();
    }
    if (sControl.object) {
      sControl.detach();
    }
  }
  getAttachingObject() {
    const tControl = this.transformRotateControl;
    const sControl = this.scaleControl;
    return tControl.object || sControl.object;
  }

  toggleWireframe(show) {
    if (!this.tiles || !this.tiles.group) {
      return;
    }
    this.tiles.group.traverse((object3d) => {
      if (object3d.material) {
        object3d.material.wireframe = show;
      }
    });
  }
  toggleTileVisible(show) {
    if (!this.tiles || !this.tiles.group) {
      return;
    }
    this.tiles.group.visible = show;
  }

  /**
   * 加载 cesium b3dms 格式的3d瓦片模型文件
   * @param url
   */
  loadB3dmsModel(url) {
    if (this._modelUrl === url) {
      return;
    }
    if (this.earthGroup) {
      this.earthGroup.removeFromParent();
      this._quat.copy(_quatZero);
    }
    this._invalidModelUrl = true;
    fetch( url, {} )
      .then( res => {
        if ( res.ok ) {
          return res.json();
        } else {
          throw new Error( `TilesRenderer: Failed to load tileset "${ url }" with status ${ res.status } : ${ res.statusText }` );
        }
      })
      .then(json => {
        const [x, y, z] = json.root.boundingVolume.box;
        offsetZ = json.root.boundingVolume.box[11] * -0.5
        console.log('offsetZ: ', offsetZ)
        offset.set(-x, -y, -z);
        this.tiles.group.position.set(-x, -y, -z);

        const cesiumCoord = new Cartesian3(x, y, z);
        const cartographic = Cartographic.fromCartesian(cesiumCoord);
        const longitude = cartographic.longitude * r2d;
        const latitude = cartographic.latitude * r2d;
        const coords = UnitsUtils.datumsToSpherical(latitude, longitude);
        this.mapView.position.set(-coords.x, -coords.y, offsetZ);
        this.mapView.updateMatrixWorld(true);
        this._invalidModelUrl = false;
        this._modelUrl = url;
        if (renderManually) {
          this.render();
        }
      })
    this.tiles = new TilesRenderer(url)
    const tiles = this.tiles
    const dracoLoader = new DRACOLoader()
    // const dracoDecoderPath = "https://unpkg.com/three@0.123.0/examples/js/libs/draco/gltf/"
    // dracoLoader.setDecoderPath( dracoDecoderPath )
    dracoLoader.setDecoderPath( DRACO_DECODER_PATH )
    const loader = new GLTFLoader(tiles.manager)
    loader.setDRACOLoader(dracoLoader)
    tiles.fetchOptions.mode = 'cors'
    tiles.manager.addHandler( /\.gltf$/, loader )
    const earthGroup = new THREE.Group()
    this.offsetParent = new THREE.Group();
    this.offsetParent.add(tiles.group);
    earthGroup.add(this.offsetParent);
    this.scene.add(earthGroup);
    this.earthGroup = earthGroup;

    // set tiles properties
    tiles.errorTarget = 12.0
    tiles.errorThreshold = 60
    tiles.loadSiblings = true
    tiles.optimizeRaycast = true
    tiles.stopAtEmptyTiles = true
    tiles.displayActiveTiles = false
    tiles.maxDepth = 15
    tiles.colorMode = 0
    tiles.setCamera(this.camera)
    tiles.setResolutionFromRenderer(this.camera, this.renderer)
  }

  // 加载 AI 规划出来的航线
  loadSprayPaths(jsonUrl) {
    if (!this.pathReader) {
      this.pathReader = new SprayPathReader(this.store, this.scene);
    }
    this.pathReader.clearSprayPathIndicators();
    this.pathReader.loadSprayPathsFromUrl(jsonUrl, this._meta.toEarchCoordMat);
  }

  loadEditData(url) {
    fetch( url, {} )
      .then( res => {
        if ( res.ok ) {
          return res.json();
        } else {
          throw new Error( `loadEditData "${ url }" with status ${ res.status } : ${ res.statusText }` );
        }
      })
      .then(json => {
        this.store.storageLoad(json);
      })
  }

  // 加载本地 自己编辑的喷涂航线
  loadSprayPathsFromLocal() {
    const pathEdit = this._addSprayPathPointPlugin;
    if (!this.pathReader) {
      this.pathReader = new SprayPathReader(this.store, this.scene)
    }
    this.pathReader.loadSprayPathFromEditPlugin(pathEdit);
  }

  generateSingleAreaPath() {
    const plugin = this._selectAreaPlugin
    const areaMesh = plugin.areaMesh
    if (!areaMesh) {
      return
    }
    const exist = this.store.sprayPathIndicators.find(it => it.path.workAreaIds.includes(areaMesh.vo.id))
    if (exist) {
      return
    }
    const cfg = SprayConfig.Default
    // cfg.direction = DirectEnum.horizontal
    // cfg.expand = 0.3
    if (this.store.flyHomePoints.length) {
      const {x, y, z} = this.store.flyHomePoints[0].position
      cfg.flyHomePoint.set(x, y, z)
    }
    const cssColor = '#' + areaMesh.material.color.getHexString()
    const convexMesh = this._surfaceGridPlugin.convexMesh
    const areas = []
    if (plugin.areaMeshes.length > 1) {
      const tempWorkAreas = plugin.createAreaMeshesBySelections()
      areas.push(...tempWorkAreas)
    } else {
      areas.push(areaMesh.vo)
    }
    areas.forEach(workArea => {
      // const sprayPath = generateRectAreaSprayPath(cfg, workArea, cssColor)
      const sprayPath = generateSprayPath(cfg, workArea, cssColor, convexMesh)
      const indicator = new SprayPathIndicator(sprayPath)
      this.scene.add(indicator)
      this.store.sprayPathIndicators.push(indicator)
    })
  }

  updateRouteOffset(val) {
    const dist = parseFloat(val)
    if (!isNaN(dist)) {
      this._addRoutePointPlugin.updateRouteHorizontalOffset(dist)
    }
  }
  updateSingleRouteOffsetType(oType) {
    this._addRoutePointPlugin.updateRouteOffsetType(oType)
  }
  updatePhiRange(start, end, cssColor = '#f00') {
    console.log('updatePhiRange, span: ', start, end)
    this._surfaceGridPlugin.expandPhi(start, end, cssColor, true)
  }
  updateThetaRange(start, end, cssColor = '#0b4') {
    console.log('updateThetaRange, span: ', start, end)
    this._surfaceGridPlugin.expandTheta(start, end, cssColor, true)
  }
  onQtSocketMsgReceived(world3d) {
    if (renderManually) {
      world3d.render();
    }
  }

  /**
   * 调用时机：收到 websocket 子任务航点集数据的推送，并且this._meta.toEarchCoordMatInv 必须完成了初始化
   * @param {[]} messages
   */
  initDroneFlyPath(messages) {
    if (this._quat.equals(_quatZero)) {
      // 姿态没有初始化化前，避免创建无人机的实时进度航线
      this._subRouteMessages = messages;
      return;
    }
    this.store.clearFlyPathIndicators();
    const droneFlyPath = new DroneFlyPath().fromAI(messages);
    // const droneFlyPath = new DroneFlyPath().fromAI(pathFromAI);
    const flyPathIndicator = new FlyPathIndicatorV2(
      droneFlyPath,
      this._meta.toEarchCoordMatInv,
    );
    this.scene.add(flyPathIndicator);
    this.store.flyPathIndicators.push(flyPathIndicator);
    const cameraPos = flyPathIndicator.firstPosition
      .clone()
      .add(droneFlyPath.facing[0].clone().negate().multiplyScalar(6));
    this.camera.position.copy(cameraPos);
    this.cameraControls.target.copy(flyPathIndicator.firstPosition);
    this.cameraControls.update();
  }

  testDroneFlyPath() {
    fetch('../json/drone_fly2.json').then(res => {
      if (res.ok) {
        return res.json()
      } else {
        throw new Error(
          `testDroneFlyPath: Failed to load drone_fly1.json`,
        );
      }
    }).then(json => {
      this.store.flyPathIndicators.forEach(it => it.removeFromParent())
      this.store.flyPathIndicators.length = 0
      // console.log(json)
      this.initDroneFlyPath(json.arr)
    })
  }

  _initDragDrop() {
    document.addEventListener('dragover', (event) => {
      event.preventDefault();
      event.dataTransfer.dropEffect = 'copy';
    });
    document.addEventListener('drop', (event) => {
      event.preventDefault();
      const file = event.dataTransfer.files[0];
      this.store.importPlaneRefs(file);
    });
  }

  initMapView() {
    let mapsProvider
    if (useMapbox) {
      const mapBoxToken = 'pk.eyJ1IjoiaGN5dGVjaCIsImEiOiJjbGhmd255d3kxYmI1M2twNG9kamV0ZTl0In0.vibkD9l0XaOozcVi478Jcw';
      mapsProvider = new MapBoxProvider(mapBoxToken, 'mapbox/satellite-streets-v12', MapBoxProvider.STYLE, 'jpg70');
    } else {
      mapsProvider = new BingMapsProvider('', BingMapsProvider.AERIAL_LABELS); // BingMapsProvider.ROAD
    }
    mapsProvider.maxZoom = 18;
    const mapView = new MapView(MapView.PLANAR, mapsProvider);
    // const mapView = new MapView(MapView.PLANAR, new DebugProvider());
    this.scene.add(mapView);
    mapView.rotation.x = Math.PI * 0.5;
    this.mapView = mapView;
    // this.moveCameraByLongiLat(103.55399172753096, 30.81934566853852); // 街子
  }

  // 返回true 正在编辑形状 并且鼠标吸附到多边形的顶点上 或者 边上
  editingVertAndAhering() {
    return this._modifyVertPlugin._currentAdhere;
  }

  _createLights() {
    // 环境光
    const ambLight = new THREE.AmbientLight('#fff', 0.2)
    this.scene.add(ambLight)
    // 半球光
    // const hemiLight = new THREE.HemisphereLight('#7198fc', '#fff')
    // this.scene.add(hemiLight)
  }

  // 调用时机：canvas画布的大小发生变化后
  _updateForDimensionChange() {
    const dom = this._dimension.dom
    if (!dom || this._dimension.sizeComputed) {
      return
    }
    const style = getComputedStyle(dom)
    const width = parseInt(style.width)
    const height = parseInt(style.height)
    if (isNaN(width) || isNaN(height)) {
      return
    }
    if (width) {
      this._dimension.sizeComputed = true
    }
    const {w, h} = this._dimension
    if (w !== width || h !== height) {
      const aspect = width / height
      this._perspCamera.aspect = aspect
      this._perspCamera.updateProjectionMatrix();
      const vs = 2 // vertical size
      const hs = vs * aspect // horizontal size
      this._orthoCamera.left = -hs
      this._orthoCamera.right = hs
      this._orthoCamera.updateProjectionMatrix()
      this.renderer.setDrawingBufferSize(width, height, window.devicePixelRatio)
      this.renderer.setSize(width, height, false)
      if (renderManually) {
        this.render();
      }
      this.outlineHelper.onCanvasResize()
    }
    this._dimension.w = width
    this._dimension.h = height
  }

  // 所有插件取消挂接
  allPluginsDetach() {
    this._plugins.forEach(plugin => plugin.detachItem())
    this.scene.dispatchEvent(World3dEvent.clearPaneSelection);
  }

  /**
   * 切换相机， 在编辑不可喷涂区域时，如果相机没有正对着孔，很容易画错位置
   * @param {Boolean} useOrtho，true 使用正交相机，false 使用透视相机
   */
  switchCamera(useOrtho) {
    if (useOrtho) {
      this._orthoCamera.position.copy(this.camera.position)
      this._orthoCamera.quaternion.copy(this.camera.quaternion)
      this._orthoCamera.updateMatrix()
      this.camera = this._orthoCamera
      this.cameraControls.enableRotate = false
    } else {
      this._perspCamera.position.copy(this.camera.position)
      this.camera = this._perspCamera
      this.cameraControls.enableRotate = true
    }
    this.cameraControls.object = this.camera
    this.cameraControls.update()
    if (renderManually) {
      this.render();
    }
  }

  _updateCameraPosition() {
    if (!cameraMoving) {
      return
    }

    if (curFrame >= animFrame) {
      cameraMoving = false
      return
    }
    curFrame++
    if (quaterionChanged) {
      this.cameraFacing.applyQuaternion(quaterionStep)
      this.camera.position.copy(this.cameraFacing).multiplyScalar(cameraDistances[curFrame - 1]).negate().add(this.cameraControls.target)
      this.camera.lookAt(this.cameraControls.target)
    } else {
      this.camera.position.add(cameraMove)
    }

    // this.editOrientation.updateEyeIndicatorsVisible()
  }

  getCameraFacing(target) {
    return target.copy(this.cameraControls.target).sub(this.camera.position).normalize();
  }

  /**
   * 通过按钮或快捷键改变相机的方向和位置，
   * @param {THREE.Vector3} orientation 相机的目标朝向，已经归一化
   * @param {THREE.Vector3} newFocus 相机的目标聚焦点
   * @param {number} distance 相机位置到聚焦点的距离，以米为单位
   */
  onCameraTargetChange(orientation, newFocus, distance) {
    this.cameraFaceTo.copy(orientation)
    this.cameraOldPosition.copy(this.camera.position)
    this.cameraFacing = this.cameraControls.target.clone().sub(this.camera.position).normalize()
    quaterionChanged = Math.abs(orientation.dot(this.cameraFacing) - 1) > 0.001
    if (quaterionChanged) { // 相机姿态有变化
      const quat = new THREE.Quaternion().setFromUnitVectors(this.cameraFacing, this.cameraFaceTo)
      quaterionStep = new THREE.Quaternion().slerp(quat, 1.0 / animFrame)
      const cameraDistance = this.camera.position.distanceTo(this.cameraControls.target)
      cameraDistances.length = 0
      for (let i = 1; i <= animFrame; i++) {
        const t = i / animFrame
        const d = THREE.MathUtils.lerp(cameraDistance, distance, t)
        cameraDistances.push(d)
      }
    } else { // 相机姿态 无变化 直接修改相机坐标
      cameraNewPosition.copy(newFocus).add(orientation.clone().negate().multiplyScalar(distance))
      cameraMove.copy(cameraNewPosition).sub(this.camera.position).divideScalar(animFrame)
    }

    this.setCameraFocus(newFocus)
    cameraMoving = true
    curFrame = 0
    if (renderManually) {
      this.render();
    }
  }

  /**
   * 设置相机焦点
   * @param {THREE.Vector3} focus
   */
  setCameraFocus(focus) {
    this.cameraControls.target.copy(focus)
  }

  hasWorkarea() {
    return this.store.areaMeshes.length > 0
  }

  setB3dmsClipPlanes(planes) {
    this.b3dmsClipPlanes.length = 0;
    this.b3dmsClipPlanes.push(...planes);
  }
  toggleTileLock(locking) {
    this._lockTileUpdate = locking;
  }

  /**
   * 渲染单帧画面
   * @param {number?} time requestAnimationFrame传入的从网页加载开始算的毫秒浮点数。默认为window.performance.now()
   */
  render(time) {
    if (this._skipRender) {
      return
    }
    if (this._usingCameraThird) {
      this.renderer.clear();
    }
    this.renderer.render(this.scene, this.camera)
    this._updateForDimensionChange()
    if (this.tiles && this.tiles.group.visible && !this._invalidModelUrl) {
      // this.offsetParent.quaternion 用于 抵消3d模型的原始姿态， this.tiles.group.position 用于抵消3d模型的原始偏移
      if (this.tiles.group.children.length && this._quat.equals(_quatZero)) {
        this.tiles.group.children[0].children[0].getWorldQuaternion(this._quat).invert()
        this.offsetParent.quaternion.copy(this._quat);
        const offsetMatrix = new THREE.Matrix4().makeTranslation(-offset.x, -offset.y, -offset.z)
        const rotateMatrix = new THREE.Matrix4().makeRotationFromQuaternion(this._quat.clone().invert())
        this._meta.toEarchCoordMat.multiplyMatrices(offsetMatrix, rotateMatrix)
        this._meta.modelEarthMat.copy(this._meta.toEarchCoordMat)
        this._meta.toEarchCoordMatInv.copy(this._meta.toEarchCoordMat).invert();
        if (this._subRouteMessages) {
          this.initDroneFlyPath(this._subRouteMessages);
          this._subRouteMessages = null;
        }
        this.offsetParent.updateMatrixWorld(true);
        this.updatePostionMarkersTips();
        this.scene.dispatchEvent(World3dEvent.modelLoaded);
      }
      this.toggleWireframe(this._showingWireframe);
      if (!this._lockTileUpdate) {
        this.tiles.update()
      }
    }
    this._updateCameraPosition()
    this._animations.forEach(animation => {
      animation.updateAni()
    })
    this._animations = this._animations.filter(it => !it.isCompleted())
    this.outlineHelper.onAfterRender();
    if (renderManually && (cameraMoving || this._animations.length)) {
      const renderFrame = this.render;
      setTimeout(() => {
        renderFrame();
      }, 0);
    }
    if (this._usingCameraThird) {
      const thirdCamera = this._cameraThird;
      thirdCamera.position.copy(this.camera.position);
      thirdCamera.position.z += 20;
      thirdCamera.rotation.copy(this.camera.rotation);
      thirdCamera.rotateOnAxis(unit_x, -Math.PI / 2);
      this.scene.add(this._eyeLocator);
      this._eyeLocator.position.copy(this.camera.position);
      const { w, h } = this._dimension;
      const port = getSubViewRect(w, h);
      this.renderer.setViewport(port);
      this.renderer.clearDepth();
      this.renderer.render(this.scene, thirdCamera);
      this.renderer.setViewport(0, 0, w, h);
      this._eyeLocator.removeFromParent();
    }
  }

  startRender() {
    if (renderManually) {
      return;
    }
    const renderOnce = (time) => {
      this.render(time)
      this.rafId = requestAnimationFrame(renderOnce)
    }
    renderOnce(window.performance.now())
  }

  skipRender(skip) {
    this._skipRender = skip
  }

  // 结束工作区喷涂动画
  cancelWorkAnimation(workArea) {
    // const animations = this._animations.filter(it => it.workAreaMeshes.includes(workArea));
    // animations.forEach(ani => ani.cancelAni());
  }

  startSetTarget() {
    this.editOrientation.beFree();
    this.store.clearAddingAreaMesh();
    this.store.clearAddingHoleMesh();
    this.store.settingFocus();
  }

  startSetCalibration() {
    this.editOrientation.beFree();
    this.store.clearAddingAreaMesh();
    this.store.clearAddingHoleMesh();
    this.store.settingCalibrationPoint();
  }

  startSetFlyHomePoint() {
    this.editOrientation.beFree();
    this.store.clearAddingAreaMesh();
    this.store.clearAddingHoleMesh();
    this.store.settingHomePoint();
  }

  // 开始添加固定视角
  settingOrientation() {
    this.store.addingFixedEye();
    this.editOrientation.startSettingOrientation();
  }
  // 开始添加 采样长方体的顶点
  settingOrientSampleCube() {
    this.store.clearSelection();
    this.store.settingSampleCubeVert();
    this.editOrientation.startSettingRect();
  }
  toggleConvexVisible(visible) {
    this._surfaceGridPlugin.toggleConvexMeshVisible(visible)
  }
  // 开始添加 三维采样空间的顶点
  addingConvex3dVert() {
    this.store.clearSelection();
    this.store.addingConvexVert();
    this._surfaceGridPlugin.startSampleCube()
  }
  sampleOutConvex3dMesh() {
    this._surfaceGridPlugin.sampleOutConvexMesh()
  }
  settingPlaneHelper(isWall) {
    PlaneRefVO.rotateOnlyZ = isWall
    this.store.clearSelection();
    this.store.settingPlaneRef()
    this.editOrientation.startAddingPlaneRef();
  }
  addingSliceLines() {
    this.store.clearSelection();
    this.store.addingSliceLine();
    // 对曲面进行标定时，需要对b3dms tile瓦片网格的lod更新进行冻结，否则会出现纰漏
    // 比如一个位于区域顶点角的三角面片被第一个切平面切割，本来应该也被第二个切平面切割，如果进行第二次切割时相机的位置和姿态发生变化，
    // 导致 tile瓦片网格的lod更新，被第一个切平面切割三角面片会隐藏，由n个不同的三角面片盖住；隐藏的三角面片就只能经过一次切割；
    // 此外，不冻结lod更新，也会影响区域之间的吸附衔接功能；两个区域之间的衔接边不能保证切割的是同一批三角面片
    this.toggleTileLock(true);
    this.editOrientation.shapeExtract.clearUndo();
    this.editOrientation.shapeExtract.clearCutCache();
  }

  // 根据采样长方体 绘制凸包轮廓
  createOrientationContour() {
    const contourCreated = this.editOrientation.sampleOutRect();
    this.transformRotateControl.detach();
    this.scaleControl.detach();
    return contourCreated;
  }
  endAddOrientaion() {
    this.store.freeState();
    this.editOrientation.stopAddOrientations();
  }

  // 开始添加工作区
  addingWorkArea() {
    this.editOrientation.beFree();
    // this.editWorkArea.startAddWorkArea()
    this.switchCamera(true);
    this.store.clearSelection();
    this.store.addingWorkArea();
    hideWorkAreaTip()
  }
  // 开始添加不可喷涂区
  addingHole() {
    // this.editWorkArea.startAddHoleArea()
    this.switchCamera(true);
    // this._selectAreaPlugin.detachItem()
    this.store.clearSelection();
    this.store.addingHole();
    hideWorkAreaTip()
  }
  // 开始编辑不可喷涂区
  editingHole() {
    // this.editWorkArea.startEditHoleArea()
    this.switchCamera(true);
    this.store.clearSelection();
    this.store.editingHole();
    hideWorkAreaTip()
  }

  startAddBlocking() {
    if (this.camera === this._orthoCamera) {
      this.switchCamera(false);
    }
    this.editOrientation.beFree();
    this.store.addingBlocking();
  }
  startAddIsoLine(isoType) {
    this.editOrientation.beFree()
    this.store.addingNormalPoints()
    this._addIsoLinePlugin.createNewIsoLine(isoType)
  }
  startAppendPointsToIsoLine() {
    const isoLine = this._addIsoLinePlugin.currentIsoLine
    if (!isoLine) {
      return
    }
    this._addIsoLinePlugin.startAddPointsToIsoLine()
    this.editOrientation.beFree()
    this.store.addingNormalPoints()
  }
  startAddRoutePoints() {
    this.editOrientation.beFree()
    this.store.addingRoutePoints()
  }
  startMoveRoutePoints() {
    this.editOrientation.beFree();
    this.store.movingRoutePoint();
  }
  setRoutePointHeading() {
    if (this.store.stateFree) {
      this._addRoutePointPlugin.setRoutePointHeading();
    }
    return this.transformRotateControl.object instanceof RoutePointIndicator
  }
  startMovingStartEnd() {
    this.store.clearSelection();
    this.store.settingMoveStartEnd();
  }

  // 修改工作区颜色
  changeAreaColor(colorStr) {
    const surfaceMesh = this.store.pickingSurfaceMesh();
    if (surfaceMesh) {
      surfaceMesh.changeConvexColor(colorStr);
    } else {
      this._selectAreaPlugin.changeSelectedAreaColor(colorStr);
    }
  }
  changeAreaSurfacing(surfacing) {
    this._selectAreaPlugin.changeSelectedAreaSurfacing(surfacing);
  }

  /**
   * 将工作区列表，视角列表保存到浏览器 localStorage
   */
  saveToLocalStorage() {
    const obj = this.store.storageSave()
    obj.modelUrl = this._modelUrl
    obj.modelName = ''
    obj.toEarchCoordMat = this._meta.toEarchCoordMat.toArray()
    obj.epsgCoord = this._meta.epsgCoord
    setLocalItem(WorldConfig.localSaveName, obj)
    return obj
  }

  /**
   * 从浏览器 localStorage 加载工作区列表，视角列表
   */
  loadFromLocalStorage() {
    const jsonObj = getLocalItem(WorldConfig.localSaveName)
    this.store.storageLoad(jsonObj)
  }

  measuringPoint() {
    this.store.settingMeasurePoint();
    this.measureUtil.startShowCoord();
  }

  /**
   * @return number, 返回相机朝向 和 正Y轴的逆时针夹角，°为单位
   */
  getCameraBearingDeg() {
    tmp3.copy(this.cameraControls.target).sub(this.camera.position).normalize();
    if (Math.abs(tmp3.z) > 0.98) {
      const radian = this.camera.rotation.z;
      const degZ = radian * r2d;
      return degZ;
    } else {
      return -getYAngleDeg(tmp3.x, tmp3.y);
    }
  }

  // 开始编辑单个工作区的喷涂动画
  editingSprayPath(single) {
    const workAreaMesh = this.store.pickingArea();
    if (workAreaMesh) {
      this.switchCamera(true);
      this._addSprayPathPointPlugin.attachItem(workAreaMesh, single);
      this.store.settingSprayPath();
    }
  }

  // 显示经过编辑的 喷涂单个工作区的动画
  showCustomSprayPathAnimation() {
    this._selectAreaPlugin.detachItem();
    const anim = new SprayPathAnimation(this);
    anim.setPathByPlugin(this._addSprayPathPointPlugin);
    this.store.clearSelection();
    this.store.freeState();
    this._animations.push(anim);
  }

  destroy() {
    cancelAnimationFrame(this.rafId)
    window.removeEventListener('keydown', this.keydownHandler)
    window.removeEventListener('resize', this.onWindowResize)
    this.scene.clear()
    this.renderer.dispose()
  }

  /**
   * 添加航线校准点
   * @param {THREE.Vector3} position
   */
  addCalibrationPoint(position) {
    const num = this.store.calibrationPoints.length + 1
    const marker = getPositionMarker(num + '', '#009d32');
    this.scene.add(marker);
    marker.position.copy(position);
    this.store.calibrationPoints.push(marker);
    const tip = this.measureUtil.getCartographicTip(position, num);
    marker.userData = { tipPos: tip.txt, coord: tip.geoCoord };
  }
  refreshCalibrationPoints() {
    const arr = this.store.calibrationPoints
    const positions = arr.map(it => it.position)
    arr.forEach(it => it.removeFromParent())
    arr.length = 0
    positions.forEach(it => this.addCalibrationPoint(it))
  }

  /**
   * 添加航线的起飞点，目前一个主航线只能添加一个航线起飞点
   * @param {THREE.Vector3} position
   */
  addOrUpdateFlyHomePoint(position) {
    const points = this.store.flyHomePoints;
    let marker;
    if (points.length) {
      marker = points[0];
    } else {
      marker = getPositionMarker('Takeoff', '#3131fd', '#d29400');
      this.scene.add(marker);
      this.store.flyHomePoints.push(marker);
    }
    const tip = this.measureUtil.getCartographicTip(position);
    marker.userData = { tipPos: tip.txt, coord: tip.geoCoord };
    marker.position.copy(position);
  }

  // 调用时机：3d tiles的部分瓦片已经绘制出来
  updatePostionMarkersTips() {
    const arr = this.store.calibrationPoints.concat(this.store.flyHomePoints);
    arr.forEach((it, index) => {
      const num = index + 1
      const tip = this.measureUtil.getCartographicTip(it.position, num);
      it.userData = { tipPos: tip.txt, coord: tip.geoCoord };
    });
  }

  _setMouseRaycaster(event, bounds) {
    const x = event.clientX - bounds.left;
    const y = bounds.top + bounds.height - event.clientY;
    let usingSubViewRay = false;
    if (this._usingCameraThird) {
      const { width, height } = bounds;
      const port = getSubViewRect(width, height);
      const bx = port.x;
      const by = port.y;
      if (x >= bx && x <= bx + port.z && y >= by && y <= by + port.w) {
        // console.log('moving on sub canvas');
        usingSubViewRay = true;
      }
    }
    this.mouseRaycaster = getMouseRaycaster(
      mouseRaycaster,
      this.camera,
      event.clientX,
      event.clientY,
      bounds,
    );
    return usingSubViewRay;
  }

  setRouteConstraintPlane(constraint) {
    this._addRoutePointPlugin.setConstraintPlane(constraint)
  }
  addRouteAdherePlane(constraint) {
    this._addRoutePointPlugin.addAdherePlane(constraint)
  }
  clearRouteAdherePlanes() {
    this._addRoutePointPlugin.clearAllAdherePlanes()
  }
  addNewRoute() {
    this._addRoutePointPlugin.createNewRoute()
    this.startAddRoutePoints()
  }
  appendRoutePoint(insertBefore) {
    this._addRoutePointPlugin.insertRoutePoint(insertBefore)
  }

  /**
   * 鼠标按下事件的回调函数
   * @param {PointerEvent} event
   * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent
   */
  pointerDown(event) {
    const bounds = event.target.getBoundingClientRect();
    startPos.set(event.clientX, event.clientY);
    pointerEverMoved = false;
    const onSub = this._setMouseRaycaster(event, bounds);
    const pressingLeft = event.button === MouseButton.left;
    this._mousedownOnSub = onSub && this._usingCameraThird && pressingLeft;
    this._eyeLocator.material.color.setStyle(
      this._mousedownOnSub ? '#004ddd' : '#fff',
    );
    if (this._mousedownOnSub) {
      this.cameraControls.enabled = false;
      dragStart.set(event.clientX, event.clientY, 1);
      oldCamPos.copy(this.camera.position);
      oldTargetPos.copy(this.cameraControls.target);
      dirForward
        .copy(this.cameraControls.target)
        .sub(this.camera.position)
        .normalize();
      dirRight.copy(negative_z).cross(dirForward).normalize();
    } else {
      this.store.setIntersectObject(event);
      this.editOrientation.pointerDown(event);
      this.editWorkArea.pointerDown(event);
      this.editHoleArea.pointerDown(event);
      this.measureUtil.pointerDown(event);
      this._plugins.forEach((plugin) => plugin.pointerDown(event));
    }
  }

  pointerMove(event) {
    this._setMouseRaycaster(event, event.target.getBoundingClientRect());
    if (this._mousedownOnSub) {
      const dx = (event.clientX - dragStart.x) * 0.5;
      const dy = (event.clientY - dragStart.y) * 0.5;
      const forward = tmp1.copy(dirForward).multiplyScalar(-dy);
      const right = tmp2.copy(dirRight).multiplyScalar(dx);
      forward.add(right);
      this.camera.position.copy(oldCamPos).add(forward);
      this.cameraControls.target.copy(oldTargetPos).add(forward);
    } else {
      this.editOrientation.pointerMove(event);
      this.editWorkArea.pointerMove(event);
      this.editHoleArea.pointerMove(event);
      this.measureUtil.pointerMove(event);
      this._plugins.forEach((plugin) => plugin.pointerMove(event));
    }
    if (!pointerEverMoved) {
      endPos.set(event.clientX, event.clientY);
      if (startPos.distanceTo(endPos) > 2) {
        pointerEverMoved = true;
      }
    }
  }

  pointerUp(event) {
    if (this._mousedownOnSub) {
      this._mousedownOnSub = false;
      this._eyeLocator.material.color.setStyle('#fff');
      return;
    }
    const store = this.store;
    if (pointerEverMoved) {
      this.editOrientation.pointerUpMoved(event);
      this.editWorkArea.pointerUpMoved(event);
      this.editHoleArea.pointerUpMoved(event);
      this._plugins.forEach((plugin) => plugin.pointerUpMoved(event));
      return;
    } else {
      if (!store._intersection && store.stateFree) {
        this.allPluginsDetach()
      }
    }
    if (store.stateMovingStartEnd && event.button === MouseButton.right) {
      store.freeState();
      return;
    }
    if (this.store.stateMovingStartEnd) {
      PathComputeUI.pointerUp4SetStartEnd(this);
      return;
    }
    if (
      (store.stateSettingFocus ||
        store.stateSettingCalibrationPoint ||
        store.stateSettingHomePoint) &&
      store.pickingTile()
    ) {
      const hitPoint = store.getIntersection().point;
      const toCamera = this.camera.position
        .clone()
        .sub(this.cameraControls.target)
        .normalize();
      this.setCameraFocus(hitPoint);
      this.camera.position.copy(hitPoint).add(toCamera.multiplyScalar(5));
      // const offset = hitPoint.clone().sub(this.cameraControls.target)
      // this.camera.position.add(offset)
      if (store.stateSettingFocus) {
        if (!posIndicator) {
          posIndicator = getPositionMarker();
          this.scene.add(posIndicator);
        }
        posIndicator.visible = true;
        posIndicator.position.copy(hitPoint);
        const event = World3dEvent.cameraTargetSelected;
        event.value.copy(hitPoint);
        this.scene.dispatchEvent(event);
        setTimeout(() => (posIndicator.visible = false), 3000);
      } else if (store.stateSettingCalibrationPoint) {
        this.addCalibrationPoint(hitPoint);
      } else {
        this.addOrUpdateFlyHomePoint(hitPoint);
      }
      this.store.freeState();
    } else {
      this.editOrientation.pointerUpUnmoved(event);
      this.editWorkArea.pointerUpUnmoved(event);
      this.editHoleArea.pointerUpUnmoved(event);
      this.measureUtil.pointerUpUnmoved(event);
      this._plugins.forEach((plugin) => plugin.pointerUpUnmoved(event));
    }
  }
}

const testUrl = './static/b3dms/gymnasium_test/tileset.json';
// const testUrl = './static/b3dms/tashui_simple/tileset.json';
// const testUrl = './static/b3dms/chengdushuita1206/tileset.json';
let world3d
function initWorld3d() {
  world3d = new World3d();
  const canvas = document.createElementNS(
    'http://www.w3.org/1999/xhtml',
    'canvas',
  );
  canvas.className = 'full-size';
  canvas.addEventListener('pointerdown', (event) => {
    world3d.pointerDown(event);
  });
  canvas.addEventListener('pointermove', (event) => {
    world3d.pointerMove(event);
  });
  canvas.addEventListener('pointerup', (event) => {
    world3d.pointerUp(event);
  });
  world3d.init(canvas);
  // world3d.initQtSocket(qtChannelUrl);
  world3d.startRender();
  world3d.loadB3dmsModel(testUrl); // 直接加载 b3dms 3d tiles
  // world3d.loadEditData(projectPlanUrl);
  //  world3d.testDroneFlyPath()
  world3d.loadFromLocalStorage();
  // world3d.loadSprayPaths(testRouteUrl);
  // world3d.loadFromRemote(WorldConfig.testedProjectId)
  // world3d.testDroneFlyPath()
}
initWorld3d()
export function getWorld3dInstance() {
  return world3d
}
